﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Properties;

namespace Borg
{
    public class Repository : RepositoryElement
    {
        protected internal readonly Dictionary<RepositoryId, PluginInfo> plugins;
        protected internal readonly Dictionary<RepositoryId, ContractInfo> contracts;
        protected internal readonly List<MonitorInfo> monitors;

        protected internal readonly Dictionary<object, ExtensionInfo> objects;

        protected internal DateTime lastUpdate;

        public event PluginEventHandler PluginAdded;
        public event ContractEventHandler ContractAdded;
        public event PluginEventHandler PluginRemoved;
        public event ContractEventHandler ContractRemoved;

        public override string Name
        {
            get { return "Repository"; }
        }

        public override Assembly Assembly
        {
            get { return Runtime.Assembly; }
        }

        public DateTime LastUpdate
        {
            get { return lastUpdate; }
        }

        public RepositoryCollection<PluginInfo> PluginInfos
        {
            get { return new RepositoryCollection<PluginInfo>(new List<PluginInfo>(plugins.Values)); }
        }

        public RepositoryCollection<ContractInfo> ContractInfos
        {
            get { return new RepositoryCollection<ContractInfo>(new List<ContractInfo>(contracts.Values)); }
        }

        public RepositoryCollection<SlotDefinition> SlotDefinitions
        {
            get
            {
                var repositoryCollection = new RepositoryCollection<SlotDefinition>(new List<SlotDefinition>());

                foreach (var contractInfo in contracts.Values)
                    repositoryCollection.AddRange(contractInfo.SlotDefinitions);

                return repositoryCollection;
            }
        }

        public RepositoryCollection<ExtensionTypeInfo> ExtensionTypeInfos
        {
            get
            {
                var repositoryCollection = new RepositoryCollection<ExtensionTypeInfo>(new List<ExtensionTypeInfo>());

                foreach (var pluginInfo in plugins.Values)
                    repositoryCollection.AddRange(pluginInfo.extensionTypeInfos);

                return repositoryCollection;
            }
        }

        public RepositoryCollection<PlugTypeInfo> PlugTypeInfos
        {
            get
            {
                var repositoryCollection = new RepositoryCollection<PlugTypeInfo>(new List<PlugTypeInfo>());

                foreach (var extensionTypeInfo in ExtensionTypeInfos)
                    repositoryCollection.AddRange(extensionTypeInfo.PlugTypeInfos);

                return repositoryCollection;
            }
        }

        public RepositoryCollection<SlotTypeInfo> SlotTypeInfos
        {
            get
            {
                var repositoryCollection = new RepositoryCollection<SlotTypeInfo>(new List<SlotTypeInfo>());

                foreach (var extensionTypeInfo in ExtensionTypeInfos)
                    repositoryCollection.AddRange(extensionTypeInfo.slotTypeInfos);

                return repositoryCollection;
            }
        }

        public RepositoryCollection<ExtensionInfo> ExtensionInfos
        {
            get
            {
                var repositoryCollection = new RepositoryCollection<ExtensionInfo>(new List<ExtensionInfo>());

                foreach (var extensionTypeInfo in ExtensionTypeInfos)
                    repositoryCollection.AddRange(extensionTypeInfo.extensionInfos);

                return repositoryCollection;
            }
        }

        public RepositoryCollection<PlugInfo> PlugInfos
        {
            get
            {
                var repositoryCollection = new RepositoryCollection<PlugInfo>(new List<PlugInfo>());

                foreach (var extensionInfo in ExtensionInfos)
                    repositoryCollection.AddRange(extensionInfo.plugInfos);

                return repositoryCollection;
            }
        }

        public RepositoryCollection<SlotInfo> SlotInfos
        {
            get
            {
                var repositoryCollection = new RepositoryCollection<SlotInfo>(new List<SlotInfo>());

                foreach (var extensionInfo in ExtensionInfos)
                    repositoryCollection.AddRange(extensionInfo.slotInfos);

                return repositoryCollection;
            }
        }

        public Repository()
        {
            plugins = new Dictionary<RepositoryId, PluginInfo>();
            contracts = new Dictionary<RepositoryId, ContractInfo>();
            monitors = new List<MonitorInfo>();
            objects = new Dictionary<object, ExtensionInfo>();
            lastUpdate = DateTime.Now;
        }

        internal void AddContract(ContractInfo contractInfo, uint taskGroupId)
        {
            Runtime.TaskQueue.Enqueue(
                TaskFactory.CreateAddContract(
                    this,
                    contractInfo,
                    taskGroupId,
                    Runtime.Assembly));
        }

        internal void AddPlugin(PluginInfo pluginInfo, uint taskGroupId)
        {
            Runtime.TaskQueue.Enqueue(
                TaskFactory.CreateAddPlugin(
                    this,
                    pluginInfo,
                    taskGroupId,
                    Runtime.Assembly));
        }

        internal void RemoveContract(ContractInfo contractInfo, uint taskGroupId)
        {
            Runtime.TaskQueue.Enqueue(
                TaskFactory.CreateRemoveContract(
                    this,
                    contractInfo,
                    taskGroupId,
                    Runtime.Assembly));
        }

        internal void RemovePlugin(PluginInfo pluginInfo, uint taskGroupId)
        {
            Runtime.TaskQueue.Enqueue(
                TaskFactory.CreateRemovePlugin(
                    this,
                    pluginInfo,
                    taskGroupId,
                    Runtime.Assembly));
        }

        internal void InternalAddContract(ContractInfo contractInfo)
        {
            contracts.Add(contractInfo.repositoryId, contractInfo);
            lastUpdate = DateTime.Now;

            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Normal,
                    Messages.ContractAdded,
                    new object[]
                        {
                            contractInfo.id.ToString(CultureInfo.InvariantCulture),
                            contractInfo.Name,
                            string.Format("\n  ({0})", contractInfo.repositoryId.Uri.OriginalString)
                        }));
        }

        internal void InternalAddContract(ContractInfo contractInfo, uint taskGroupId)
        {
            if (contracts.ContainsKey(contractInfo.repositoryId))
                return;

            InternalAddContract(contractInfo);

            foreach (var slotDefinition in contractInfo.slotDefinitions)
            {
                foreach (var plugType in PlugTypeInfos.Find(slotDefinition.Name))
                {
                    if (plugType.IsQualified ||
                        (plugType.qualificationState &
                        QualificationState.PlugSlotDefinitionNotFound) !=
                        QualificationState.PlugSlotDefinitionNotFound)
                        continue;

                    var pti = plugType;
                    var stat = (long)(pti.qualificationState | QualificationState.QualificationMissing);
                    pti.qualificationState = (QualificationState)stat;
                }

                foreach (var slotTypeInfo in SlotTypeInfos.
                    Find(slotDefinition.Name).
                    Where(slotTypeInfo => !slotTypeInfo.IsQualified &&
                        !slotTypeInfo.IsQualificationMissing &&
                        (slotTypeInfo.qualificationState &
                        QualificationState.SlotSlotDefinitionNotFound) ==
                        QualificationState.SlotSlotDefinitionNotFound))
                {
                    slotTypeInfo.ForceQualify();

                    if (!slotTypeInfo.IsQualified || !Runtime.AutoOpen)
                        continue;

                    var tasks = (from extensionInfo in slotTypeInfo.ExtensionTypeInfo.extensionInfos
                                 from slotInfo in extensionInfo.slotInfos
                                 where slotInfo.slotTypeInfo == slotTypeInfo
                                 select TaskFactory.CreateOpenSlot(slotInfo, false, taskGroupId, Runtime.Assembly)).ToList();

                    Runtime.TaskQueue.Enqueue(tasks);
                }
            }

            if (ContractAdded == null)
                return;

            Runtime.InvokeEventHandler(
                "ContractAdded",
                ContractAdded,
                this,
                new ContractEventArgs(
                    contractInfo,
                    Runtime.assembly));
        }

        internal void InternalAddPlugin(PluginInfo pluginInfo)
        {
            plugins.Add(pluginInfo.repositoryId, pluginInfo);
            lastUpdate = DateTime.Now;

            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Normal,
                    Messages.PluginAdded,
                    new object[]
                        {
                            pluginInfo.id.ToString(CultureInfo.InvariantCulture),
                            pluginInfo.Name,
                            string.Format("\n  ({0})", pluginInfo.repositoryId.Uri.OriginalString)
                        }));

            foreach (var monitorInfo in monitors)
                monitorInfo.SubscribePluginInfo(pluginInfo);

            if (PluginAdded == null)
                return;

            Runtime.InvokeEventHandler(
                "PluginAdded",
                PluginAdded,
                this,
                new PluginEventArgs(
                    pluginInfo,
                    Runtime.assembly));
        }

        internal void InternalAddPlugin(PluginInfo pluginInfo, uint taskGroupId)
        {
            if (plugins.ContainsKey(pluginInfo.repositoryId))
                return;

            InternalAddPlugin(pluginInfo);

            Runtime.TaskQueue.Enqueue(
                TaskFactory.CreateRegister(
                    pluginInfo,
                    false,
                    taskGroupId,
                    Runtime.assembly));
        }

        internal void InternalRemoveContract(ContractInfo contractInfo)
        {
            for (var i = contractInfo.slotDefinitions.Count - 1; i >= 0; --i)
                InternalRemoveSlotDefinition(contractInfo.slotDefinitions[i], Runtime.assembly);

            contracts.Remove(contractInfo.repositoryId);
            lastUpdate = DateTime.Now;

            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Diagnostic,
                    Messages.ContractRemoved,
                    new object[]
                        {
                            contractInfo.id.ToString(CultureInfo.InvariantCulture),
                            contractInfo.Name,
                            string.Format("\n  ({0})", contractInfo.repositoryId.Uri.OriginalString)
                        }));

            if (ContractRemoved == null)
                return;

            Runtime.InvokeEventHandler(
                "ContractRemoved",
                ContractRemoved,
                this,
                new ContractEventArgs(
                    contractInfo,
                    Runtime.assembly));
        }

        internal void InternalRemovePlugin(PluginInfo pluginInfo)
        {
            foreach (var extensionTypeInfo in pluginInfo.extensionTypeInfos)
                for (var i = extensionTypeInfo.extensionInfos.Count - 1; i >= 0; --i)
                    extensionTypeInfo.extensionInfos[i].InternalRelease(Runtime.assembly, true);

            foreach (var extensionTypeInfo in pluginInfo.extensionTypeInfos)
                extensionTypeInfo.InternalDeregisterPlugs(Runtime.assembly, true);

            plugins.Remove(pluginInfo.repositoryId);
            lastUpdate = DateTime.Now;

            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Diagnostic,
                    Messages.PluginRemoved,
                    new object[]
                        {
                            pluginInfo.id.ToString(CultureInfo.InvariantCulture),
                            pluginInfo.Name,
                            string.Format("\n  ({0})", pluginInfo.repositoryId.Uri.OriginalString)
                        }));

            if (PluginRemoved != null)
                Runtime.InvokeEventHandler(
                    "PluginRemoved",
                    PluginRemoved,
                    this,
                    new PluginEventArgs(
                        pluginInfo,
                        Runtime.assembly));

            foreach (var monitorInfo in monitors)
                monitorInfo.UnsubscribePluginInfo(pluginInfo);
        }

        public SlotDefinition CreateSlotDefinition(string name, SlotDefinition parent)
        {
            if (SlotDefinitions[name] != null)
            {
                Runtime.OnLogEvent(
                    this,
                    new LogEventArgs(
                        MessageType.Info,
                        VerbosityLevel.Normal,
                        Messages.ErrorOnCreatingSlotDefinition,
                        new object[]
                            {
                                name,
                                Messages.SlotDefinitionAlreadyExists
                            }));
                return null;
            }

            if (parent == null)
            {
                Runtime.OnLogEvent(
                    this,
                    new LogEventArgs(
                        MessageType.Info,
                        VerbosityLevel.Normal,
                        Messages.ErrorOnCreatingSlotDefinition,
                        new object[]
                        {
                            name,
                            Messages.ParentSlotDefinitionIsNotDefined
                        }));

                return null;
            }

            var contractInfo = parent.ContractInfo;

            var slotDefinition =
                new SlotDefinition(
                    contractInfo,
                    new SlotDefinitionAttribute(name),
                    parent.type)
                {
                    paramDefinitions = parent.paramDefinitions,
                    derivedFrom = parent
                };

            contractInfo.slotDefinitions.Add(slotDefinition);
            lastUpdate = DateTime.Now;
            contractInfo.OnPropertyChanged("SlotDefinitions");

            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Diagnostic,
                    Messages.SlotDefinitionCreated,
                    new object[]
                        {
                            slotDefinition.id.ToString(CultureInfo.InvariantCulture),
                            slotDefinition.Name
                        }));

            slotDefinition.ForceQualify();
            return slotDefinition;
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void RemoveSlotDefinition(SlotDefinition sd)
        {
            if (sd == null || sd.id == 1 || sd.id == 2)
                return;

            InternalRemoveSlotDefinition(sd, Assembly.GetCallingAssembly());
        }

        internal void InternalRemoveSlotDefinition(SlotDefinition slotDef, Assembly initiator)
        {
            var contractInfo = slotDef.ContractInfo;

            if (contractInfo.Name != "Borg.dll" && contractInfo.slotDefinitions.Remove(slotDef))
                lastUpdate = DateTime.Now;

            contractInfo.OnPropertyChanged("SlotDefinitions");

            foreach (var extensionTypeInfo in ExtensionTypeInfos)
            {
                for (var i = extensionTypeInfo.extensionInfos.Count - 1; i >= 0; --i)
                {
                    foreach (var slotInfo in extensionTypeInfo.extensionInfos[i].slotInfos)
                    {
                        if (slotInfo.slotTypeInfo.slotDefinition != slotDef)
                            continue;

                        var slotTypeInfo = slotInfo.slotTypeInfo;
                        var stat = (long)(slotTypeInfo.qualificationState | QualificationState.QualificationMissing);
                        slotTypeInfo.qualificationState = (QualificationState)stat;
                        slotInfo.slotTypeInfo.OnPropertyChanged("IsQualificationMissing");
                        slotInfo.slotTypeInfo.OnPropertyChanged("IsQualified");
                        slotInfo.InternalClose(Runtime.assembly, true);
                    }
                }

                foreach (var pti in extensionTypeInfo.plugTypeInfos)
                {
                    if (pti.slotDefinition != slotDef)
                        continue;

                    var plugTypeInfo = pti;
                    var stat = (long)(plugTypeInfo.qualificationState | QualificationState.QualificationMissing);
                    plugTypeInfo.qualificationState = (QualificationState)stat;
                    pti.OnPropertyChanged("IsQualificationMissing");
                    pti.OnPropertyChanged("IsQualified");
                }
            }

            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Diagnostic,
                    Messages.SlotDefinitionRemoved,
                    new object[]
                        {
                            slotDef.id.ToString(CultureInfo.InvariantCulture),
                            slotDef.Name
                        }));
        }

        internal SlotDefinition GetQualifiedSlotDefinition(string slotDefName)
        {
            var slotDefinitions = Runtime.Repository.SlotDefinitions.Find(slotDefName);

            foreach (var slotDefinition in slotDefinitions)
            {
                if (slotDefinition.IsQualified)
                {
                    if (slotDefinitions.Length > 1)
                        Runtime.OnLogEvent(
                            this,
                            new LogEventArgs(
                                MessageType.Warning,
                                VerbosityLevel.Normal,
                                Messages.WarningOnLookUpSlotDefinition,
                                new object[]
                                    {
                                        slotDefinition.Name,
                                        string.Format("\n  {0}", Messages.SlotDefinitionIsNotUnique)
                                    }));

                    return slotDefinition;
                }

                if (!slotDefinition.IsQualificationMissing)
                    continue;

                slotDefinition.ForceQualify();

                if (slotDefinition.IsQualified)
                    return slotDefinition;
            }

            return null;
        }

        internal delegate void PluginInfoHandler(PluginInfo pi, uint taskGroupId);

        internal delegate void ContractInfoHandler(ContractInfo ci, uint taskGroupId);
    }
}

